package com.lbh.graphics;

import com.jogamp.nativewindow.NativeWindow;
import com.jogamp.nativewindow.NativeWindowFactory;
import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration;
import com.jogamp.opengl.*;
import com.jogamp.opengl.fixedfunc.GLMatrixFunc;
import com.jogamp.opengl.glu.GLU;
import com.jogamp.opengl.util.gl2.GLUT;
import com.lbh.Utility;
import com.lbh.structures.common.GameObject;
import com.lbh.structures.components.physics.Rigidbody;
import com.lbh.structures.math.Quaternion;
import com.lbh.structures.math.Vector3;
import com.lbh.structures.components.render.Mesh;
import com.lbh.time.Time;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/***
 * project:JRuntime
 * package:com.lbh.graphics
 *
 * @author user:lenovo QQ:1601078366
 * @date 2021/10/21 1:12
 * @version V1.0
 */
@SuppressWarnings({"Duplicates", "FieldCanBeLocal", "unused", "SpellCheckingInspection", "AlibabaLowerCamelCaseVariableNaming"})
public class RenderCanvas extends Canvas {

    private GLDrawable drawable;
    private GLContext ctx;
    private GLU glu;
    private GLUT glut;
    private int repaintNum;

    private Vector3 camPos = new Vector3(0.0F, 0.0F, -5.0F);
    private GameObject go;
    private float theta = 0.0F;
    // lookAt 0.0 0.0 1.0
    // x 向右，y 向上，z 向前

    private boolean initialized = false;
    private void initGLOnce(GL2 gl){
        if (initialized){
            return;
        }
        GLProgram program = new GLProgram(gl);
        program.attachShader(gl, GL2.GL_VERTEX_SHADER, "/vert.glsl");
        program.attachShader(gl, GL2.GL_FRAGMENT_SHADER, "/frag.glsl");
        program.link(gl);
        gl.glProgramUniform2f(program.program, program.getUniform(gl, "resolution"), 512.0F, 512.0F);
        go.getComponent(Mesh.class).setProgram(gl, program);
        go.addComponent(Rigidbody.class);
        initialized = true;
        Time.reset();
        Utility.storeJSON(go, "/test.json");
    }

    public RenderCanvas(AWTGraphicsConfiguration config) {
        super(unwrap(config));
        NativeWindowFactory.getFactory(getClass());
        NativeWindow win = NativeWindowFactory.getNativeWindow(this, config);
        GLCapabilities glCaps = (GLCapabilities) win.getGraphicsConfiguration().getNativeGraphicsConfiguration().getChosenCapabilities();
        drawable = GLDrawableFactory.getFactory(glCaps.getGLProfile()).createGLDrawable(win);
        ctx = drawable.createContext(null);
        glu = new GLU();
        glut = new GLUT();
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.getKeyCode()){
                    case KeyEvent.VK_W:
                        camPos.z += 0.5F;
                        break;
                    case KeyEvent.VK_A:
                        camPos.x -= 0.5F;
                        break;
                    case KeyEvent.VK_S:
                        camPos.z -= 0.5F;
                        break;
                    case KeyEvent.VK_D:
                        camPos.x += 0.5F;
                        break;
                    default:
                        System.out.println(e.getKeyCode());
                        break;
                }
            }
        });
        go = GameObject.createPrimitive(Mesh.MESH_PRIMITIVE_CUBE);
    }

    private void initGLConfig(GL2 gl, int x, int y, int width, int height){
        gl.glViewport(x, y, width, height);
        gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glEnable(GL.GL_BLEND);
        gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE);
    }

    private void initCamera(GL2 gl, int x, int y, int width, int height){
        float[] mat = new float[16];

        gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(60.0, ((double)width) / height, 0.1, 100.0);

        gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
        gl.glLoadIdentity();
        glu.gluLookAt(camPos.x, camPos.y, camPos.z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

        gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
        gl.glPushMatrix();
        gl.glGetFloatv(GLMatrixFunc.GL_PROJECTION_MATRIX, mat, 0);
        gl.glLoadIdentity();

        gl.glMultMatrixf(mat, 0);

        gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
        gl.glPushMatrix();
        gl.glGetFloatv(GLMatrixFunc.GL_MODELVIEW_MATRIX, mat, 0);
        gl.glLoadIdentity();

        gl.glMultMatrixf(mat, 0);
    }

    private void clearAndDraw(GL2 gl, int x, int y, int width, int height) {
        initGLOnce(gl);

        initGLConfig(gl, x, y, width, height);

        gl.glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
        gl.glClearDepth(1.0);

        gl.glColorMask(true, true, true, true);
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

        initCamera(gl, x, y, width, height);

        go.update();

        gl.glPushMatrix();
        gl.glTranslatef(0.0F, 0.0F, 0.0F);
        gl.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        go.render(gl);
        go.transform.rotation.mul(Quaternion.axisAngle(new Vector3(1.0F, 1.0F, 1.0F), 1.0F));
        gl.glPopMatrix();
    }

    private static GraphicsConfiguration unwrap(AWTGraphicsConfiguration config) {
        if (config == null) {
            return null;
        }
        return config.getAWTGraphicsConfiguration();
    }

    @Override
    public void addNotify() {
        super.addNotify();
        drawable.setRealized(true);
    }

    @Override
    public void removeNotify() {
        ctx.destroy();
        drawable.setRealized(false);
    }

    @Override
    public void update(Graphics g) {
        paint(g);
    }

    @Override
    public void paint(Graphics g) {
        int width = getWidth();
        int height = getHeight();
        ++repaintNum;
        int res = ctx.makeCurrent();
        if (res != GLContext.CONTEXT_NOT_CURRENT) {
            Time.update();
            clearAndDraw(ctx.getGL().getGL2(), 0, 0, width, height);
            ctx.release();
        }
        drawable.swapBuffers();
    }
}
